package com.gin.no_gay_alliance.fragment.reply;

import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;

import com.gin.jackson.utils.JacksonUtils;
import com.gin.jackson.utils.ObjectUtils;
import com.gin.nga.bbscode.utils.BbsTagParser;
import com.gin.nga.exception.NgaClientException;
import com.gin.nga.exception.NgaException;
import com.gin.nga.params.post.PrepareParam;
import com.gin.nga.params.read.ReadReplyParam;
import com.gin.nga.params.read.ReadTopicParam;
import com.gin.nga.response.post.PrepareBody;
import com.gin.no_gay_alliance.R;
import com.gin.no_gay_alliance.activity.TopicReadActivity;
import com.gin.no_gay_alliance.adpter.reply.ReplyItemAdapter;
import com.gin.no_gay_alliance.app.MyApplication;
import com.gin.no_gay_alliance.callback.Callback;
import com.gin.no_gay_alliance.database.entity.ReplyHistory;
import com.gin.no_gay_alliance.databinding.DialogRecyclerViewReplyBinding;
import com.gin.no_gay_alliance.databinding.FragmentReplyPage2Binding;
import com.gin.no_gay_alliance.enums.MessageWhat;
import com.gin.no_gay_alliance.exception.RequireParamException;
import com.gin.no_gay_alliance.fragment.base.BaseBindingFragment;
import com.gin.no_gay_alliance.listener.MyScrollListener;
import com.gin.no_gay_alliance.manager.BlockDataManager;
import com.gin.no_gay_alliance.manager.TopicReadManager;
import com.gin.no_gay_alliance.param.TopicReadParam;
import com.gin.no_gay_alliance.utils.AlertDialogUtils;
import com.gin.no_gay_alliance.viewmodel.ReadItem;
import com.gin.no_gay_alliance.viewmodel.ReplyItem;
import com.gin.no_gay_alliance.viewmodel.ScrollTarget;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import okhttp3.Call;

/**
 * 一页回复
 */
public class ReplyPageFragment extends BaseBindingFragment<FragmentReplyPage2Binding> implements MyScrollListener.OnDraggingListener, MyScrollListener.OnDraggedListener, Callback<ReadItem> {
    private static final String TAG = ReplyPageFragment.class.getSimpleName();
    private static final String ARG_PARAM = TopicReadParam.class.getName();
    private static final String MESSAGE_REPLY_NOT_FOUND = "未找到目标回复, 可能: 审核中/审核不通过/隐藏/被屏蔽";
    /**
     * 缺失楼层的填充正文
     */
    private static final String LOST_FLOOR_CONTENT = "[b][color=red]本条回复不可见,可能: 被隐藏/审核中/审核不通过[/color][/b]";
    /**
     * 参数
     */
    private TopicReadParam param;
    /**
     * 适配器
     */
    private ReplyItemAdapter adapter;
    private List<ReplyItem> blockedReplies = new ArrayList<>();
    private boolean ignoreCache;

    /**
     * onCreateView 的后续操作
     */
    @Override
    protected void onCreateView() {
        // 组件初始化
        b.tvWarning.setVisibility(View.GONE);
        adapter = new ReplyItemAdapter(requireActivity(), new ArrayList<>());
        b.recyclerView.setAdapter(adapter);
        b.swipeRefreshLayout.setOnRefreshListener(this::refresh);
        MyScrollListener scrollListener = new MyScrollListener();
        scrollListener.setOnDraggingListener(this);
        scrollListener.setOnDraggedListener(this);
        b.recyclerView.addOnScrollListener(scrollListener);

        // 发送请求
        sendRequest(false);
    }

    /**
     * 获取和处理参数
     *
     * @param arguments 参数
     */
    @Override
    protected void handleArguments(Bundle arguments) {
        if (arguments != null && arguments.getParcelable(ARG_PARAM) != null) {
            param = arguments.getParcelable(ARG_PARAM);
        } else {
            throw new RequireParamException(ARG_PARAM);
        }
        JacksonUtils.print(param);
    }

    /**
     * 创建选项菜单
     *
     * @param menu     菜单
     * @param inflater 渲染器
     */
    @Override
    public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
        super.onCreateOptionsMenu(menu, inflater);
        inflater.inflate(R.menu.block_menu, menu);
    }

    /**
     * 点击选项按钮的操作
     *
     * @param item 选中的按钮
     * @return 是否消耗
     */
    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        // 弹窗显示被屏蔽的主题列表
        if (item.getItemId() == R.id.menu_item_block) {
            DialogRecyclerViewReplyBinding bb = MyApplication.app.managerFactory.getViewBindingManager().getDialogView(DialogRecyclerViewReplyBinding.class, requireContext());
            ReplyItemAdapter blockedReplyAdapter = new ReplyItemAdapter(requireActivity(), blockedReplies);
            blockedReplyAdapter.setBlockNetworkImage(false);
            bb.recyclerView.setAdapter(blockedReplyAdapter);


            AlertDialog dialog = AlertDialogUtils.showConfirm(requireContext(), getString(R.string.blocked_replies), null, bb.getRoot(), (d, which) -> {
                if (which == DialogInterface.BUTTON_POSITIVE) {

                }
            });

            dialog.setOnDismissListener(d -> {
                //关闭时移除弹窗
                ViewGroup parent = (ViewGroup) bb.getRoot().getParent();
                parent.removeView(bb.getRoot());
            });
            return true;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    public void onPause() {
        super.onPause();

        // 离开页面时，修改”上次浏览的页码“
        updatePage();

    }

    @Override
    public void onPrepareOptionsMenu(@NonNull Menu menu) {
        super.onPrepareOptionsMenu(menu);
        // 显示被屏蔽的回复数
        MenuItem menuItem = menu.findItem(R.id.menu_item_block);
        if (menuItem != null) {
            menuItem.setVisible(!ObjectUtils.isEmpty(blockedReplies));
            menuItem.setTitle(String.format(Locale.CHINA, "屏蔽 (%d)", blockedReplies.size()));
        }
    }

    /**
     * 刷新当前页
     */
    public void refresh() {
        sendRequest(true);
    }

    /**
     * 拖动结束
     *
     * @param dx x方向的总距离
     * @param dy y方向的总距离
     */
    @Override
    public void onDragged(int dx, int dy) {
        // 拖动结束时解锁翻页
        getParentHandler().sendEmptyMessage(MessageWhat.UNLOCK_VIEW_PAGER);
        // 解锁图片加载
        unblockImage();

        //如果拖动距离为0，切换右下角按钮的显示状态
        if (dy == 0) {
            getParentHandler().sendEmptyMessage(MessageWhat.TOGGLE_BUTTON_VISIBILITY);
        }
    }

    /**
     * 正在拖动
     *
     * @param dx 目前x方向的总距离
     * @param dy 目前y方向的总距离
     */
    @Override
    public void onDragging(int dx, int dy) {
        // 拖动距离大于50时，锁定上级翻页, 阻止图片加载
        if (Math.abs(dy) >= 10) {
            getParentHandler().sendEmptyMessage(MessageWhat.LOCK_VIEW_PAGER);
            blockImage();
        }
        if (dy > 50) {
            getParentHandler().sendEmptyMessage(MessageWhat.HIDE_BUTTON);
        }
        if (dy < -50) {
            getParentHandler().sendEmptyMessage(MessageWhat.SHOW_BUTTON);
        }

    }

    /**
     * 请求成功回调
     *
     * @param data 数据
     */
    @Override
    public void onSuccess(ReadItem data) {
        if (ignoreCache) {
            toast("刷新成功");
        }

        // 预处理 查询历史版本
        handleVersions(data.replyItems, data.topicId);
        // 预处理缺失楼层
        handleLostFloor(data);
        //  将响应抛出给父级
        getParentHandler().sendMessage(MessageWhat.ON_SUCCESS_TOPIC_READ_REQUEST, data);
        //设置当前版面id，供操作按钮使用
        adapter.setForumId(data.forumId);
        // 回复列表
        List<ReplyItem> replyItems = data.replyItems;
        // 获取屏蔽设置
        MyApplication.app.managerFactory.getBlockDataManager().blockDataList(false, blockData -> {
            // 被屏蔽的回复
            blockedReplies = replyItems.stream().filter(i -> BlockDataManager.isBlocked(blockData, i)).collect(Collectors.toList());
            if (!blockedReplies.isEmpty()) {
                Log.d(TAG, String.format(Locale.CHINA, "屏蔽了 %d 个回复", blockedReplies.size()));
            }
            // 正常回复
            List<ReplyItem> replies = replyItems.stream().filter(i -> !BlockDataManager.isBlocked(blockData, i)).collect(Collectors.toList());
            // 渲染数据
            if (handler != null) {
                ui(() -> {
                    b.tvWarning.setVisibility(View.GONE);
                    b.recyclerView.setVisibility(View.VISIBLE);
                    adapter.updateData(replies);
                    handleScrollTarget(replies);
                });
            }
        });
    }

    /**
     * 请求之前执行的操作
     */
    @Override
    public void beforeRequest() {
        // 阻止图片加载
        blockImage();
        // 启动加载动画
        ui(() -> b.swipeRefreshLayout.setRefreshing(true));
    }

    /**
     * 请求失败回调(一般为网络访问错误)
     *
     * @param call call
     * @param e    错误
     */
    @Override
    public void onFailure(@NonNull Call call, @NonNull IOException e) {
        Callback.super.onFailure(call, e);
        handleException(e.getMessage());
    }

    /**
     * 处理Nga异常
     *
     * @param e 异常
     */
    @Override
    public void handleException(NgaException e) {
        Callback.super.handleException(e);
        if (e instanceof NgaClientException) {
            String t0 = ((NgaClientException) e).getReason().get(0);
            String t1 = ((NgaClientException) e).getReason().get(1);
            handleException(!ObjectUtils.isEmpty(t0) ? t0 : t1);
        }
    }

    /**
     * 阻止图片加载
     */
    private void blockImage() {
//        Log.d(TAG, "锁定图片加载");
        adapter.setBlockNetworkImage(true);
    }

    /**
     * 显示报错信息，隐藏其他组件
     *
     * @param text 信息
     */
    private void handleException(String text) {
        onRequestFinally();
        ui(() -> {
            b.tvWarning.setText(text);
            b.tvWarning.setVisibility(View.VISIBLE);
            b.recyclerView.setVisibility(View.GONE);
        });
    }

    /**
     * 预处理，为不可见楼层填充占位数据
     *
     * @param data data
     */
    private void handleLostFloor(ReadItem data) {
        // 楼层总数
        Integer total = data.total != null ? data.total : param.total;
        // 从本页楼层中取最大值
        if (total == null && !ObjectUtils.isEmpty(data.replyItems)) {
            int max = data.replyItems.stream().filter(i -> i.floorNumber != null).mapToInt(i -> i.floorNumber).max().orElse(-1);
            if (max != -1) {
                total = max;
            }
        }
        // 如果没获取到总楼层数，且当前为最新页，放弃处理
        boolean isMaxPage = Objects.equals(data.page, data.totalPage);
        if (total == null && isMaxPage) {
            return;
        }
        // 计算本页的楼层号范围
        // 起始
        int start = (data.page - 1) * data.size;
        // 结束
        int end = isMaxPage ? Math.min(total - 1, data.page * data.size - 1) : start + 19;
        // 实际获取的楼层号
        List<Integer> foundFloorNumbers = data.replyItems.stream().map(i -> i.floorNumber).collect(Collectors.toList());
        // 缺失的楼层号
        List<Integer> notFoundFloorNumbers = new ArrayList<>();
        for (int i = start; i <= end; i++) {
            if (!foundFloorNumbers.contains(i)) {
                notFoundFloorNumbers.add(i);
            }
        }

        if (!notFoundFloorNumbers.isEmpty()) {
            Log.d(TAG, String.format(Locale.CHINA, "本页楼层范围: %d ~ %d , 缺失的楼层号: %s", start, end, notFoundFloorNumbers));
            for (Integer num : notFoundFloorNumbers) {
                ReplyItem item = new ReplyItem();
                item.floorNumber = num;
                item.nodes = BbsTagParser.parseContent(LOST_FLOOR_CONTENT);
                data.replyItems.add(item);
            }
            // 重新排序
            data.replyItems.sort(Comparator.comparingInt(o -> o.floorNumber));
        }
    }

    /**
     * 处理滚动目标
     *
     * @param replyItems 回复列表
     */
    private void handleScrollTarget(List<ReplyItem> replyItems) {
        // 处理滚动请求
        ScrollTarget scrollTarget = getScrollTarget();
        if (scrollTarget != null && scrollTarget.page == param.page) {
            // 有滚动目标, 且匹配当前页
            Log.d(TAG, "onSuccess: " + scrollTarget);
            // 目标的位置
            int targetIndex = findTargetIndex(scrollTarget, replyItems);
            if (targetIndex != -1) {
                // 找到了目标位置 滚动到该位置
                scrollToPosition(targetIndex);
            } else {
                // 未找到目标位置
                // 如果是回复目标,发送请求确认其可见性,如果可见提示可能被屏蔽,如果不可见，提示并滚动到最近的replyID
                if (scrollTarget.replyId != null) {
                    // 发送请求检查可见性
                    PrepareParam prepareParam = PrepareParam.replyParam(param.topicId, scrollTarget.replyId, false);
                    MyApplication.app.managerFactory.getPostManager().prepare(prepareParam, new Callback<PrepareBody>() {
                        @Override
                        public void onSuccess(PrepareBody data) {
                            toast("该回复对你可见, 但可能被你屏蔽了");
                            onRequestFinally();
                        }

                        @Override
                        public void handleException(NgaException e) {
                            Callback.super.handleException(e);
                            if (e instanceof NgaClientException) {
                                // 查找目标附近的位置
                                int nearbyIndex = findNearbyIndex(scrollTarget, replyItems);
                                if (nearbyIndex != -1) {
                                    // 找到了附近回复，滚动
                                    scrollToPosition(nearbyIndex);
                                    toast("已滚动到附近位置");
                                } else {
                                    // 未找到附近回复，提示
                                    toast("未找到附近位置");
                                    onRequestFinally();
                                }
                            }
                        }
                    });
                } else if (scrollTarget.floorNumber != null) {
                    // 如果是楼层目标, 提示不可见原因
                    toast(MESSAGE_REPLY_NOT_FOUND);
                    onRequestFinally();
                } else {
                    // 其他情况，请求结束
                    onRequestFinally();
                }
            }
        } else {
            Log.d(TAG, "onSuccess: 没有滚动目标，或页数不匹配，请求结束");
            onRequestFinally();
        }
    }

    /**
     * 预处理，查询历史版本
     *
     * @param replyItems 回复列表
     * @param topicId    主题id
     */
    private void handleVersions(List<ReplyItem> replyItems, long topicId) {
        // 回复id集合
        Set<Long> replyIds = replyItems.stream().map(i -> i.replyId).filter(Objects::nonNull).collect(Collectors.toSet());
        // 数据库中的历史回复
        List<ReplyHistory> replyHistories = MyApplication.app.ngaDatabase.replyHistoryDao().listByReplyId(replyIds, topicId);

        for (ReplyItem replyItem : replyItems) {
            if (replyItem.replyId == null || replyItem.topicId == null) {
                continue;
            }
            // 当前这条的uuid
            String uuid = new ReplyHistory(replyItem).uuid;
            // 从查询结果中过滤出回复id相同 但uuid不同的历史版本
            replyItem.versions = replyHistories.stream()
                    .filter(i -> i.replyId == replyItem.replyId && !uuid.equals(i.uuid))
                    .collect(Collectors.toList());
        }
    }

    /**
     * 请求结束的操作, 解锁图片加载，关闭加载动画
     */
    private void onRequestFinally() {
        // 消耗目标
        if (requireActivity() instanceof TopicReadActivity) {
            ((TopicReadActivity) requireActivity()).clearScrollTarget();
        }
        // 解锁图片加载
        unblockImage();
        // 关闭加载动画
        uiDelayed(() -> b.swipeRefreshLayout.setRefreshing(false), 200);
    }

    /**
     * recyclerView 滚动到指定位置
     */
    private void scrollToPosition(int position) {
        // TODO: 2023/9/9 目前无法准确滚动到位
        Log.d(TAG, "滚动到位置: " + position);
        // 先滚动到最后位置
        ui(() -> {
            b.recyclerView.scrollToPosition(adapter.getItemCount() - 1);
            uiDelayed(() -> {
                b.recyclerView.scrollToPosition(adapter.getItemCount() - 1);
                // 再滚动到目标位置
                uiDelayed(() -> {
                    b.recyclerView.scrollToPosition(position);
                    onRequestFinally();
                }, 200);
            }, 200);
        });

    }

    /**
     * 发送请求
     *
     * @param ignoreCache 是否忽略缓存
     */
    private void sendRequest(boolean ignoreCache) {
        TopicReadManager manager = MyApplication.app.managerFactory.getTopicReadManager();
        this.ignoreCache = ignoreCache;
        if (param.backupId != null) {
            manager.readReply(ignoreCache, new ReadReplyParam(param.topicId, param.replyId, param.backupId), this);
        } else if (param.replyId == null) {
            manager.readTopic(ignoreCache, new ReadTopicParam(param.topicId, param.page, param.authorUid), this);
        } else {
            manager.readReply(ignoreCache,new ReadReplyParam( param.replyId), this);
        }
    }

    /**
     * 解锁图片加载
     */
    private void unblockImage() {
//        Log.d(TAG, "解锁图片加载");
        adapter.setBlockNetworkImage(false);
    }

    /**
     * 离开页面时，修改”上次浏览的页码“
     */
    private void updatePage() {
        if (param.topicId != null && param.page != null) {
            Log.d(TAG, String.format(Locale.CHINA, "主题: %d, 上次浏览到 %d 页", param.topicId, param.page));
            MyApplication.app.ngaDatabase.topicHistoryDao().updatePage(param.topicId, param.page);
        }
    }

    /**
     * 实例化
     *
     * @param param 参数
     * @return Fragment
     */
    public static ReplyPageFragment newInstance(TopicReadParam param) {
        ReplyPageFragment fragment = new ReplyPageFragment();
        Bundle args = new Bundle();
        args.putParcelable(ARG_PARAM, param);
        fragment.setArguments(args);
        return fragment;
    }


    /**
     * 从回复列表中找到滚动目标所在的位置
     *
     * @param scrollTarget 滚动目标
     * @param replyItems   回复列表
     * @return 位置
     */
    private static int findTargetIndex(ScrollTarget scrollTarget, List<ReplyItem> replyItems) {
        for (int i = 0; i < replyItems.size(); i++) {
            ReplyItem item = replyItems.get(i);
            if (scrollTarget.replyId != null && Objects.equals(scrollTarget.replyId, item.replyId)) {
                return i;
            }
            if (scrollTarget.floorNumber != null && Objects.equals(scrollTarget.floorNumber, item.floorNumber)) {
                return i;
            }
        }
        return -1;
    }

    /**
     * 从回复列表中找到离滚动目标最近的位置
     *
     * @param scrollTarget 滚动目标
     * @param replyItems   回复列表
     * @return 位置
     */
    private static int findNearbyIndex(ScrollTarget scrollTarget, List<ReplyItem> replyItems) {
        //ID距离
        long rangeId = Long.MAX_VALUE;
        // 楼层距离
        int rangeFloor = Integer.MAX_VALUE;

        for (int i = 0; i < replyItems.size(); i++) {
            ReplyItem item = replyItems.get(i);
            // 如果为回复目标
            if (scrollTarget.replyId != null && item.replyId != null) {
                // 当前距离
                long range = Math.abs(item.replyId - scrollTarget.replyId);
                // 如果之前记录的距离大于当前距离，记录当前距离, 继续比较下一个回复
                if (range < rangeId) {
                    rangeId = range;
                } else {
                    // 否则返回当前位置
                    return i;
                }
            }
            // 如果为楼层目标
            if (scrollTarget.floorNumber != null && item.floorNumber != null) {
                // 当前距离
                int range = Math.abs(item.floorNumber - scrollTarget.floorNumber);
                // 如果之前记录的距离大于当前距离，记录当前距离, 继续比较下一个回复
                if (range < rangeFloor) {
                    rangeId = range;
                } else {
                    // 否则返回当前位置
                    return i;
                }
            }
        }
        return -1;
    }

    /**
     * 获取滚动目标
     *
     * @return 滚动目标
     */
    private ScrollTarget getScrollTarget() {
        if (requireActivity() instanceof TopicReadActivity) {
            return ((TopicReadActivity) requireActivity()).getScrollTarget();
        }
        return null;
    }
}
